package net.aiion.weave.impl.startup.resources;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Logger;

import net.aiion.weave.impl.cards.transformation.CardPropertiesTransformation;
import net.aiion.weave.impl.conversions.transformation.ConverterTransformation;
import net.aiion.weave.impl.injection.transformation.InjectionTransformation;
import net.aiion.weave.impl.injection.transformation.InjectorTransformation;
import net.aiion.weave.impl.interception.transformation.InterceptableTransformation;
import net.aiion.weave.impl.interception.transformation.InterceptorTransformation;
import net.aiion.weave.impl.messaging.transformation.MessageReceiverTransformation;
import net.aiion.weave.impl.naming.transformation.ContextualTransformation;
import net.aiion.weave.impl.painters.transformation.PainterTransformation;
import net.aiion.weave.impl.ui.transformation.BindableTransformation;
import net.aiion.weave.spi.bytecode.Bytecode;
import net.aiion.weave.spi.bytecode.BytecodeSource;
import net.aiion.weave.spi.bytecode.ResumptionCondition;
import net.aiion.weave.spi.resources.Resource;
import net.aiion.weave.spi.resources.ResourceListener;

public class StartupClassResourceListener implements ResourceListener {
    private static final Logger LOGGER = Logger.getLogger(StartupClassResourceListener.class.getName());

    private static final String[] EXCLUDED_PACKAGES = { "java.", "javax.", "sun.", "com.sun.", "javassist.", "junit.",
        "org.junit.", "org.hamcrest.", "net.aiion.weave.asm." };

    private Map<Resource, ResumptionCondition> postponedClassResources;

    public StartupClassResourceListener() {
    }

    @Override
    public void onStartup() {
        postponedClassResources = new HashMap<>();
    }

    @Override
    public void resourceFound(final Resource aResource) {
        if (!aResource.getName().endsWith(".class") || !includesResource(aResource) || excludesResource(aResource)) {
            return;
        }

        for (Iterator<Resource> it = postponedClassResources.keySet().iterator(); it.hasNext();) {
            final Resource resource = it.next();
            if (postponedClassResources.get(resource).shouldResume()) {
                transformClassResource(resource);
                it.remove();
            }
        }

        transformClassResource(aResource);
    }

    private void transformClassResource(final Resource aResource) {
        final BytecodeSource bytecodeSource = Bytecode.createReader(aResource);
        bytecodeSource.addSink(new ContextualTransformation());
        bytecodeSource.addSink(new InjectionTransformation());
        bytecodeSource.addSink(new InterceptableTransformation());

        bytecodeSource.addSink(new InjectorTransformation());
        bytecodeSource.addSink(new InterceptorTransformation());

        bytecodeSource.addSink(new BindableTransformation());
        bytecodeSource.addSink(new CardPropertiesTransformation());
        bytecodeSource.addSink(new ConverterTransformation());
        bytecodeSource.addSink(new MessageReceiverTransformation());
        bytecodeSource.addSink(new PainterTransformation());
        bytecodeSource.addSink(Bytecode.createWriter()); // this will update the BytecodeStore
        bytecodeSource.startSourcing();

        if (bytecodeSource.isPostponed()) {
            postponedClassResources.put(aResource, bytecodeSource.getResumptionCondition());
        }
    }

    private boolean includesResource(final Resource aResource) {
        return aResource.getName().startsWith("net.aiion.weave.");
    }

    private boolean excludesResource(final Resource aResource) {
        for (String excludedPackage : EXCLUDED_PACKAGES) {
            if (aResource.getName().startsWith(excludedPackage)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public void onShutdown() {
        for (Iterator<Resource> it = postponedClassResources.keySet().iterator(); it.hasNext();) {
            final Resource resource = it.next();
            if (postponedClassResources.get(resource).shouldResume()) {
                transformClassResource(resource);
                it.remove();
            }
        }

        for (Iterator<Resource> it = postponedClassResources.keySet().iterator(); it.hasNext();) {
            LOGGER.warning(String.format("Processing of resource '%s' is still postponed "
                + "and will not be handled anymore.", it.next().getName()));
        }

        postponedClassResources = null;
    }
}
